﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using NearForums.Configuration;

namespace NearForums.DataAccess.Xml
{
    class TopicsDataAccess : BaseDataAccess, ITopicsDataAccess
    {
        public void Add(Topic topic, string ip)
        {
            // Add topic (each topic is an xml file within the topics folder)
            // topic metadata is stored in topics.xml
            string filePath = GetDataPath(TopicsFile);
            XDocument doc = XDocument.Load(filePath);
            int count = (int)doc.Root.Attribute("Count");
            topic.Id = ++count;

            // create a new xml file for the topic
            string topicXml = @"<Topic></Topic>";
            XDocument topicDoc = XDocument.Parse(topicXml);
            topicDoc.Root.SetAttributeValue("Id", topic.Id);
            topicDoc.Root.SetAttributeValue("ShortName", topic.ShortName);

            XElement descElem = new XElement("Description", SiteConfiguration.HtmlEncoder(topic.Description));
            topicDoc.Root.Add(descElem);

            XElement msgsElem = new XElement("Messages");
            topicDoc.Root.Add(msgsElem);

            XElement subElem = new XElement("Subscriptions");
            topicDoc.Root.Add(subElem);

            string filePath2 = GetDataPath(string.Format(TopicFile, topic.Id));
            topicDoc.Save(filePath2);

            // add the topic metadata in topics file
            XElement elem = new XElement("Topic",
                                new XElement("Id", topic.Id),
                                new XElement("Title", topic.Title),
                                new XElement("ShortName", topic.ShortName),
                                new XElement("CreationDate", ToString(DateTime.Now)),
                                new XElement("LastEditDate", ToString(DateTime.Now)),
                                new XElement("Views", topic.Views),
                                new XElement("Replies", topic.Replies),
                                new XElement("UserId", topic.User.Id),
                                new XElement("Tags", topic.Tags.ToString()),
                                new XElement("ForumId", topic.Forum.Id),
                                new XElement("ForumShortName", topic.Forum.ShortName),
                                new XElement("LastEditUser", topic.LastMessage == null ? topic.User.Id : topic.LastMessage.User.Id),
                                new XElement("LastEditIp", ip),
                                new XElement("Active", true),
                                new XElement("IsClose", false),
                                new XElement("Order", 0),
                                new XElement("LastMessageId", topic.LastMessage == null ? null : (int?)topic.LastMessage.Id),
                                new XElement("ReadAccessGroupId", topic.ReadAccessRole),
                                new XElement("PostAccessGroupId", topic.PostAccessRole)
                                );

            doc.Root.AddFirst(elem);
            doc.Root.SetAttributeValue("Count", count);
            doc.Save(filePath);

            TagsDataAccess tda = new TagsDataAccess();
            foreach (string tag in topic.Tags)
                tda.Add(tag, topic.Id);

            ForumsDataAccess fda = new ForumsDataAccess();
            fda.AddTopic(topic.Forum.Id);
        }

        public void AddVisit(int topicId)
        {
            string filePath = GetDataPath(TopicsFile);
            XDocument doc = XDocument.Load(filePath);
            var qry = from t in doc.Root.Elements("Topic")
                      where (int)t.Element("Id") == topicId
                      select t;
            XElement elem = qry.SingleOrDefault();
            if (elem != null)
            {
                elem.SetElementValue("Views", (int)elem.Element("Views") + 1);
                doc.Save(filePath);
            }
        }

        internal void AddReply(int topicId, int count = 1)
        {
            string filePath = GetDataPath(TopicsFile);
            XDocument doc = XDocument.Load(filePath);
            var qry = from t in doc.Root.Elements("Topic")
                      where (int)t.Element("Id") == topicId
                      select t;
            XElement elem = qry.SingleOrDefault();
            if (elem != null)
            {
                elem.SetElementValue("Replies", (int)elem.Element("Replies") + count);
                doc.Save(filePath);
            }
        }

        public void Close(int id, int userId, string ip)
        {
            string filePath = GetDataPath(TopicsFile);
            XDocument doc = XDocument.Load(filePath);
            var qry = from t in doc.Root.Elements("Topic")
                      where (int)t.Element("Id") == id
                      select t;
            var elem = qry.SingleOrDefault();
            if (elem != null)
            {
                elem.SetElementValue("IsClose", true);
                elem.SetElementValue("LastEditUser", userId);
                elem.SetElementValue("LastEditIp", ip);
                doc.Save(filePath);
            }
        }

        public void Delete(int id, int userId, string ip)
        {
            // let us do a hard-delete to save space
            string filePath = string.Format(GetDataPath(TopicFile), id);
            XDocument doc = XDocument.Load(filePath);
            int msgCount = doc.Root.Element("Messages").Elements("Message").Count();
            if (File.Exists(filePath))
                File.Delete(filePath);

            string filePath2 = GetDataPath(TopicsFile);
            XDocument doc2 = XDocument.Load(filePath2);
            var qry = from t in doc2.Root.Elements("Topic")
                      where (int)t.Element("Id") == id
                      select t;
            var elem = qry.SingleOrDefault();

            if (elem != null)
            {
                int forumId = 0;
                forumId = (int)elem.Element("ForumId");
                elem.Remove();
                doc2.Save(filePath2);

                ForumsDataAccess fda = new ForumsDataAccess();
                fda.AddTopic(forumId, -1);
                fda.AddMessage(forumId, -msgCount);

                TagsDataAccess tda = new TagsDataAccess();
                tda.RemoveByTopic(id);
            }
        }

        public void Edit(Topic topic, string ip)
        {
            string filePath = string.Format(GetDataPath(TopicFile), topic.Id);
            XDocument doc = XDocument.Load(filePath);
            doc.Root.SetElementValue("Description", SiteConfiguration.HtmlEncoder(topic.Description));
            doc.Save(filePath);

            string filePath2 = GetDataPath(TopicsFile);
            XDocument doc2 = XDocument.Load(filePath2);
            var qry = from t in doc2.Root.Elements("Topic")
                      where (int)t.Element("Id") == topic.Id
                      select t;
            XElement elem = qry.SingleOrDefault();
            if (elem != null)
            {
                elem.SetElementValue("Title", topic.Title);
                elem.SetElementValue("LastEditDate", ToString(DateTime.Now));
                elem.SetElementValue("LastEditIp", ip);
                elem.SetElementValue("Tags", topic.Tags.ToString());
                elem.SetElementValue("ReadAccessGroupId", topic.ReadAccessRole);
                elem.SetElementValue("PostAccessGroupId", topic.PostAccessRole);
                doc2.Save(filePath2);

                TagsDataAccess tda = new TagsDataAccess();
                tda.RemoveByTopic(topic.Id);
                foreach (var tag in topic.Tags)
                    tda.Add(tag, topic.Id);
            }
        }

        public Topic Get(int id)
        {
            return Get(id, true);
        }

        internal Topic Get(int id, bool getMessages)
        {
            XDocument doc = XDocument.Load(GetDataPath(TopicsFile));
            var qry = from t in doc.Root.Elements("Topic")
                      where (int)t.Element("Id") == id
                      select t;

            XElement topicElem = qry.SingleOrDefault();
            Topic topic = null;
            if (topicElem != null)
            {
                topic = GetTopicFromElement(topicElem, getMessages);
            }

            return topic;
        }

        public List<Topic> GetByForum(int forumId, int startIndex, int length, UserRole? role)
        {
            // ordered by most viewed
            XDocument doc = XDocument.Load(GetDataPath(TopicsFile));
            var qry = from t in doc.Root.Elements("Topic")
                      where (int)t.Element("ForumId") == forumId
                      orderby (int)t.Element("Views") descending
                      select t;

            IEnumerable<XElement> topicElems;
            if (startIndex > 0)
                topicElems = qry.Skip(startIndex).Take(length);
            else
                topicElems = qry.Take(length);

            List<Topic> topics = new List<Topic>();
            foreach (XElement topicElem in topicElems)
            {
                Topic topic = GetTopicFromElement(topicElem);
                int topicRole = topic.ReadAccessRole.HasValue ? (int)topic.ReadAccessRole.Value : 0;
                int userRole = role.HasValue ? (int)role.Value : 0;
                if (topicRole <= userRole)
                {
                    topics.Add(topic);
                }
            }
            return topics;
        }

        public List<Topic> GetByForumLatest(int forumId, int startIndex, int length, UserRole? role)
        {
            // latest by time
            // Description is not picked up in this.
            // Last message is not picked up in this.
            XDocument doc = XDocument.Load(GetDataPath(TopicsFile));
            var qry = from t in doc.Root.Elements("Topic")
                      where (int)t.Element("ForumId") == forumId
                      orderby ToTime((string)t.Element("LastEditDate")) descending
                      select t;

            IEnumerable<XElement> topicElems;
            if (startIndex > 0)
                topicElems = qry.Skip(startIndex).Take(length);
            else
                topicElems = qry.Take(length);

            List<Topic> topics = new List<Topic>();
            foreach (XElement topicElem in topicElems)
            {
                Topic topic = GetTopicFromElement(topicElem);
                int topicRole = topic.ReadAccessRole.HasValue ? (int)topic.ReadAccessRole.Value : 0;
                int userRole = role.HasValue ? (int)role.Value : 0;
                if (topicRole <= userRole)
                {
                    topics.Add(topic);
                }
            }
            return topics;
        }

        public List<Topic> GetByTag(string tag, int forumId, UserRole? role)
        {
            TagsDataAccess tda = new TagsDataAccess();
            List<int> topicIds = tda.GetTopics(tag);
            var topics = new List<Topic>();
            foreach (var topicId in topicIds)
            {
                Topic topic = Get(topicId);
                int topicRole = topic.ReadAccessRole.HasValue ? (int)topic.ReadAccessRole : 0;
                int userRole = role.HasValue ? (int)role.Value : 0;
                if ((userRole >= topicRole) && (topic.Forum.Id == forumId))
                    topics.Add(topic);
            }
            return topics;
        }

        public List<Topic> GetByUser(int userId, UserRole? role)
        {
            XDocument doc = XDocument.Load(GetDataPath(TopicsFile));
            var qry = from t in doc.Root.Elements("Topic")
                      where (int)t.Element("UserId") == userId
                      orderby ToTime((string)t.Element("LastEditDate")) descending
                      select t;

            List<Topic> topics = new List<Topic>();
            foreach (var topicElem in qry)
            {
                topics.Add(GetTopicFromElement(topicElem));
            }

            return topics;
        }

        public List<Topic> GetLatest()
        {
            throw new NotImplementedException();
        }

        public List<Topic> GetRelatedTopics(Topic topic, int amount)
        {
            var doc = XDocument.Load(GetDataPath(TagsFile));
            TagList tagList = topic.Tags;
            var topicIds = new List<int>();
            foreach (var tag in tagList)
            {
                var qry = from t in doc.Root.Elements("Tag")
                          where (string)t.Attribute("Name") == tag
                          select (int)t.Attribute("TopicId");

                topicIds.AddRange(qry);
            }

            var relQry = from tid in topicIds
                         group tid by tid into g
                         let count = g.Count()
                         orderby count descending
                         select new
                         {
                             TopicId = g.Key,
                             Count = count
                         };

            var relTopicIds = relQry.Take(amount);

            var relTopics = new List<Topic>();
            foreach (var rti in relTopicIds)
            {
                if (rti.TopicId != topic.Id)
                    relTopics.Add(Get(rti.TopicId));
            }

            return relTopics;
        }

        public List<Topic> GetTopicsAndMessagesByUser(int userId)
        {
            var doc = XDocument.Load(GetDataPath(MessagesFile));
            var qry = from m in doc.Root.Elements("Message")
                      where (int)m.Attribute("UserId") == userId
                      select m;
            var topicMap = new Dictionary<int, Topic>();
            foreach (var elem in qry)
            {
                int topicId = (int)elem.Attribute("TopicId");
                int messageId = (int)elem.Attribute("Id");
                Topic topic = null;
                if (topicMap.ContainsKey(topicId))
                {
                    topic = topicMap[topicId];
                }
                else
                {
                    topic = Get(topicId, false);
                    topicMap[topicId] = topic;
                }
                if (topic.Messages == null)
                    topic.Messages = new List<Message>();
                MessagesDataAccess mda = new MessagesDataAccess();
                topic.Messages.Add(mda.GetMessage(topicId, messageId));
            }

            var topics = new List<Topic>();
            foreach (var topic in topicMap.Values)
            {
                topics.Add(topic);
            }

            return topics;
        }

        public List<Topic> GetUnanswered()
        {
            // Get all unanswered from all forum
            string filePath = GetDataPath(TopicsFile);
            XDocument doc = XDocument.Load(filePath);
            var qry = from t in doc.Root.Elements("Topic")
                      where (int)t.Element("Replies") == 0
                      select t;
            List<Topic> topics = new List<Topic>();
            foreach (var elem in qry)
            {
                topics.Add(GetTopicFromElement(elem));
            }
            return topics;
        }

        public List<Topic> GetUnanswered(int forumId, UserRole? role)
        {
            XDocument doc = XDocument.Load(GetDataPath(TopicsFile));
            var qry = from t in doc.Root.Elements("Topic")
                      where (int)t.Element("ForumId") == forumId
                      && (int)t.Element("Replies") == 0
                      orderby ToTime((string)t.Element("LastEditDate")) descending
                      select t;

            IEnumerable<XElement> topicElems = qry.ToList();

            List<Topic> topics = new List<Topic>();
            foreach (XElement topicElem in topicElems)
            {
                Topic topic = GetTopicFromElement(topicElem);
                int topicRole = topic.ReadAccessRole.HasValue ? (int)topic.ReadAccessRole.Value : 0;
                int userRole = role.HasValue ? (int)role.Value : 0;
                if (topicRole <= userRole)
                {
                    topics.Add(topic);
                }
            }

            return topics;
        }

        public void Move(int id, int forumId, int userId, string ip)
        {
            // Moves the topic to a new forum id (by moderator)

            string filePath = GetDataPath(TopicsFile);
            XDocument doc = XDocument.Load(filePath);
            var qry = from t in doc.Root.Elements("Topic")
                      where (int)t.Element("Id") == id
                      select t;
            XElement elem = qry.SingleOrDefault();
            if (elem != null)
            {
                int oldForumId = (int)elem.Element("ForumId");
                elem.SetElementValue("ForumId", forumId);
                elem.SetElementValue("LastEditUser", userId);
                elem.SetElementValue("LastEditIp", ip);
                doc.Save(filePath);


                ForumsDataAccess fda = new ForumsDataAccess();
                fda.AddTopic(forumId);
                fda.AddTopic(oldForumId, -1);

                // get count of message
                XDocument doc2 = XDocument.Load(string.Format(GetDataPath(TopicFile), id));
                int msgCount = doc2.Root.Element("Messages").Elements("Message").Count();
                fda.AddMessage(forumId, msgCount);
                fda.AddMessage(oldForumId, -msgCount);
            }
        }

        public void Open(int id, int userId, string ip)
        {
            string filePath = GetDataPath(TopicsFile);
            XDocument doc = XDocument.Load(filePath);
            var qry = from t in doc.Root.Elements("Topic")
                      where (int)t.Element("Id") == id
                      select t;
            var elem = qry.SingleOrDefault();
            if (elem != null)
            {
                elem.SetElementValue("IsClose", false);
                elem.SetElementValue("LastEditUser", userId);
                elem.SetElementValue("LastEditIp", ip);
                doc.Save(filePath);
            }
        }

        // do all processing here.
        private Topic GetTopicFromElement(XElement t, bool getMessages = true)
        {
            UsersDataAccess uda = new UsersDataAccess();
            ForumsDataAccess fda = new ForumsDataAccess();
            MessagesDataAccess mda = new MessagesDataAccess();

            Topic topic = new Topic();
            topic.Id = (int)t.Element("Id");
            topic.Title = (string)t.Element("Title");
            topic.ShortName = (string)t.Element("ShortName");
            topic.Description = GetDescription(topic.Id);
            topic.Views = (int)t.Element("Views");
            topic.Replies = (int)t.Element("Replies");
            topic.Date = ToTime((string)t.Element("LastEditDate"));
            topic.User = uda.Get((int)t.Element("UserId"));
            topic.Tags = new TagList((string)t.Element("Tags"));
            topic.Forum = fda.GetByShortName((string)t.Element("ForumShortName"));
            topic.IsClosed = (bool)t.Element("IsClose");
            topic.ReadAccessRole = null;
            string readAccessRole = (string)t.Element("ReadAccessGroupId");
            if (!string.IsNullOrEmpty(readAccessRole))
            {
                topic.ReadAccessRole = (UserRole)Enum.Parse(typeof(UserRole), readAccessRole);
            }
            topic.PostAccessRole = (UserRole)Enum.Parse(typeof(UserRole), (string)t.Element("PostAccessGroupId"));
            if (getMessages)
            {
                topic.Messages = mda.GetByTopic(topic.Id);
                topic.LastMessage = topic.Messages.FirstOrDefault();
            }

            return topic;
        }

        private string GetDescription(int id)
        {
            string filePath = GetDataPath(string.Format(TopicFile, id.ToString()));
            XDocument doc = XDocument.Load(filePath);
            return SiteConfiguration.HtmlDecoder((string)doc.Root.Element("Description"));
        }
    }
}
